﻿using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.ComponentModel.DataAnnotations;
using System.Linq;
using System.Linq.Expressions;

namespace WirelessAccessPoint.Core
{
    /// <summary>
    /// Modelの基底クラスです。
    /// </summary>
    public abstract class ModelBase : INotifyPropertyChanged, IDataErrorInfo, IDisposable
    {
        private readonly static Dictionary<string, PropertyChangedEventArgs> NAME_EVENT_ARGS_DICTIONARY = new Dictionary<string, PropertyChangedEventArgs>();

        ~ModelBase()
        {
            this.Dispose(false);
        }

        public void Dispose()
        {
            this.Dispose(true);
            GC.SuppressFinalize(this);
        }

        protected virtual void Dispose(bool disposing)
        {
        }

        /// <summary>
        /// プロパティ値が変更されると発生します。
        /// </summary>
        public event PropertyChangedEventHandler PropertyChanged;

        /// <summary>
        /// PropertyChangedイベントを発生させます。
        /// </summary>
        /// <param name="propertyExpression">
        /// () => Property形式のラムダ式。
        /// このプロパティのプロパティ名はPropertyChangedイベントのイベント引数に設定されます。
        /// </param>
        protected virtual void OnPropertyChanged<TResult>(Expression<Func<TResult>> propertyExpression)
        {
            var memberExpression = propertyExpression.Body as MemberExpression;
            this.OnPropertyChanged(memberExpression.Member.Name);
        }

        /// <summary>
        /// PropertyChangedイベントを発生させます。
        /// </summary>
        /// <param name="propertyName">
        /// プロパティ名。
        /// プロパティ名はPropertyChangedイベントのイベント引数に設定されます。
        /// </param>
        protected virtual void OnPropertyChanged(string propertyName)
        {
            PropertyChangedEventArgs propertyEventArgs;

            if (!NAME_EVENT_ARGS_DICTIONARY.TryGetValue(propertyName, out propertyEventArgs))
            {
                propertyEventArgs = new PropertyChangedEventArgs(propertyName);
                NAME_EVENT_ARGS_DICTIONARY[propertyName] = propertyEventArgs;
            }

            this.OnPropertyChanged(propertyEventArgs);
        }

        /// <summary>
        /// PropertyChangedイベントを発生させます。
        /// </summary>
        /// <param name="propertyEventArgs">
        /// PropertyChangedイベントのイベント引数。
        /// </param>
        protected virtual void OnPropertyChanged(PropertyChangedEventArgs propertyEventArgs)
        {
            if (this.PropertyChanged != null)
            {
                this.PropertyChanged(this, propertyEventArgs);
            }
        }

        /// <summary>
        /// このオブジェクトに対するエラーメッセージを取得します。
        /// </summary>
        public string Error
        {
            get
            {
                var results = new List<ValidationResult>();

                if (Validator.TryValidateObject(this, new ValidationContext(this, null, null), results, true))
                {
                    return null;
                }

                return string.Join(Environment.NewLine, results.Select(r => r.ErrorMessage));
            }
        }

        /// <summary>
        /// このオブジェクトのプロパティに対するエラーメッセージを取得します。
        /// </summary>
        /// <param name="columnName">
        /// エラーメッセージを取得するプロパティのプロパティ名。
        /// </param>
        public string this[string columnName]
        {
            get
            {
                var results = new List<ValidationResult>();

                if (Validator.TryValidateProperty(
                        TypeDescriptor.GetProperties(this, null)[columnName].GetValue(this),
                        new ValidationContext(this, null, null) { MemberName = columnName },
                        results))
                {
                    return null;
                }

                return results.FirstOrDefault().ErrorMessage;
            }
        }
    }
}
